package com.yida.gateway.filter;

import java.math.BigDecimal;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.yida.common.core.constant.CacheConstants;
import com.yida.common.core.enums.EnableTypeEnum;
import com.yida.common.core.utils.ip.IpUtils;
import com.yida.common.redis.service.RedisService;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import reactor.core.publisher.Mono;

/**
 * @Description 网关全局IP黑名单过滤
 * @Author wws
 * @Date 2023-03-22
 */
@Component
public class BlackListGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(BlackListGlobalFilter.class);

    @Resource
    private RedisService redisService;

    /**
     * 过滤器核⼼⽅法
     * 
     * @param exchange 封装了request和response对象的上下⽂
     * @param chain ⽹关过滤器链（包含全局过滤器和单路由过滤器）
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 思路：获取客户端ip，判断是否在⿊名单中，在的话就拒绝访问，不在的话就放⾏
        // 从上下⽂中取出request和response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 从request对象中获取客户端ip
        // String clientIp = request.getRemoteAddress().getHostString();
        String clientIp = IpUtils.getIpAddr(request);
        log.info("BlackListGlobalFilter filter check ip: " + clientIp);
        // clientIp = localhostToIp(clientIp);
        String blackWhiteKey = CacheConstants.SYS_CONFIG_KEY + CacheConstants.SYS_CONFIG_BLACK_WHITE_KEY;
        // 判断黑白名单是否开启
        if (redisService.hasKey(blackWhiteKey)) {
            // if (EnableTypeEnum.LOCK.getType().equals((String)redisTemplate.opsForValue().get(blackWhiteKey))) {
            if (EnableTypeEnum.LOCK.getType().equals(redisService.getCacheObject(blackWhiteKey))) {
                return chain.filter(exchange);
            }
        }
        // 判断IP是否是白名单中
        Set<String> set = redisService.getCacheSet(CacheConstants.WHITE_LIST_KEY);
        if (set.contains(clientIp)) {
            return chain.filter(exchange);
        }
        // 拿着clientIp去黑名单中查询，存在的话就据绝访问
        if (redisService.hasKey(CacheConstants.BLACK_LIST + CacheConstants.REDIS_SUFFIX + clientIp)) {
            // 决绝访问，返回
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
            String data = clientIp + "黑名单IP拒绝请求，请联系管理！";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        if (checkIpVisit(clientIp)) {
            // 决绝访问，返回
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
            // String data = "Request be denied!";
            // String data = "PPV 就决定是你了！WX:Pirate-qz5971！P:13615977660";
            String data = "请求被拒绝，请联系管理员！";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        // 合法请求，放行，执行后续的过滤器
        return chain.filter(exchange);
    }

    /**
     * 返回值表示当前过滤器的顺序(优先级)，数值越⼩，优先级越⾼
     */
    @Override
    public int getOrder() {
        return -201;
    }

    /**
     * 非黑白名单访问次数超过限定次数拉黑
     * 
     * @param clientIp 访问IP地址
     * @return
     */

    private Boolean checkIpVisit(String clientIp) {
        // 校验黑名单配置是否开启
        if (redisService.hasKey(CacheConstants.BLACK_CONFIG_KEY)) {
            // Object blackConfigObj = redisTemplate.opsForValue().get(CacheConstants.BLACK_CONFIG_KEY);
            Object blackConfigObj = redisService.getCacheObject(CacheConstants.BLACK_CONFIG_KEY);
            JSONObject blackConfig = new JSONObject(blackConfigObj);
            if (ObjectUtil.isEmpty(blackConfig)) {
                return Boolean.FALSE;
            }
            // 读取黑名单配置
            Integer visits = blackConfig.get("visits", Integer.class);
            Integer visitInterval = blackConfig.get("visitInterval", Integer.class);
            BigDecimal lockTime = blackConfig.get("lockTime", BigDecimal.class);
            // 设置未完全放过请求
            if (ObjectUtil.isEmpty(visits) || ObjectUtil.isEmpty(visitInterval) || ObjectUtil.isEmpty(lockTime)) {
                return Boolean.FALSE;
            }
            String clientIpKey = CacheConstants.IP_VISIT + CacheConstants.REDIS_SUFFIX + clientIp;
            // 判断客户端IP是否存在缓存中
            if (redisService.hasKey(clientIpKey)) {
                // 访问次数自增1次
                Long clientVisits = redisService.getCacheObject(clientIpKey);
                // 获取过期时间
                Long limitTime = redisService.getExpire(clientIpKey);
                // 删除缓存再加入
                // redisTemplate.delete(clientIpKey);
                redisService.deleteObject(clientIpKey);
                // redisTemplate.opsForValue().set(clientIpKey, clientVisits + 1L, limitTime, TimeUnit.SECONDS);
                redisService.setCacheObject(clientIpKey, clientVisits + 1L, limitTime, TimeUnit.SECONDS);
                // 判断访问次数是否超过上限
                if (clientVisits < visits) {
                    return Boolean.FALSE;
                } else {
                    // 超过上限加入黑名单中
                    // redisTemplate.opsForValue().set(CacheConstants.BLACK_LIST + CacheConstants.REDIS_SUFFIX +
                    // clientIp,
                    // clientIp, lockTime.longValue(), TimeUnit.MINUTES);
                    redisService.setCacheObject(CacheConstants.BLACK_LIST + CacheConstants.REDIS_SUFFIX + clientIp, clientIp, lockTime.longValue(), TimeUnit.MINUTES);
                    return Boolean.TRUE;
                }
            } else {
                // 未存在加入缓存中
                redisService.setCacheObject(clientIpKey, 1L, Long.valueOf(visitInterval), TimeUnit.SECONDS);
                return Boolean.FALSE;
            }
        } else {
            return Boolean.FALSE;
        }
    }

    /**
     * 本地IP地址转换
     * 
     * @param ipAddress IP地址
     * @return
     */
    public String localhostToIp(String ipAddress) {
        if ("0:0:0:0:0:0:0:1".equals(ipAddress)) {
            return "127.0.0.1";
        }
        return ipAddress;
    }

}
